package com.zxy.supplier_system.server.service

import com.zxy.supplier_system.server.controller.GoodsController
import com.zxy.supplier_system.server.entity.*
import com.zxy.supplier_system.server.repository.*
import com.zxy.supplier_system.server.utils.*
import org.springframework.beans.factory.annotation.Value
import org.springframework.core.io.Resource
import org.springframework.dao.DataIntegrityViolationException
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException
import java.math.BigDecimal
import java.time.OffsetDateTime

@Service
class GoodsService(
    private val goodsRepository: GoodsRepository,
    private val brandRepository: GoodsBrandRepository,
    private val categoryRepository: GoodsCategoryRepository,
    private val systemServiceService: SystemServiceService,
    private val employeeOperateRecordService: EmployeeOperateRecordService,
    private val employeeService: EmployeeService,
    @Value("classpath:excel/templates/导出商品模板.xlsx")
    private val resource: Resource,
    private val goodsUnitRepository: GoodsUnitRepository,
) {

    @Transactional
    fun addGoods(addGoodsRequest: GoodsController.AddGoodsRequest) {
        try {
            val goods = this.goodsRepository.save(Goods().apply {
                val currentSystemService = systemServiceService.getCurrentSystemService()
                @Suppress("DuplicatedCode")
                name = addGoodsRequest.name
                barcode = addGoodsRequest.barcode
                price = addGoodsRequest.price
                amount = addGoodsRequest.amount
                brand = addGoodsRequest.brandId?.let {
                    brandRepository.findByIdAndSystemService(it, currentSystemService)
                }
                category = addGoodsRequest.categoryId?.let {
                    categoryRepository.findByIdAndSystemService(it, currentSystemService)
                }
                boxSpecification = addGoodsRequest.boxSpecification

                systemService = currentSystemService
                costPrice = addGoodsRequest.costPrice
                registrationCertificateNumber = addGoodsRequest.registrationCertificateNumber
                recordFilingNumber = addGoodsRequest.recordFilingNumber
                expressionPeriod = addGoodsRequest.expressionPeriod
                note = addGoodsRequest.note.nullIfBlank()
                executiveStandard = addGoodsRequest.executiveStandard
            })
            goods.units = addGoodsRequest.units?.map {
                GoodsUnit().apply {
                    id = it.id
                    name = it.name
                    basicMultiple = it.basicMultiple
                    this.goods = goods
                }
            }?.toMutableList() ?: mutableListOf()
            this.goodsRepository.save(goods)
            goods.subGoodsList = addGoodsRequest.subGoodsList.map {
                SubGoods().apply {
                    number = it.number
                    name = it.name
                    amount = it.amount
                    this.goods = goods
                }
            }.toMutableList()
            this.employeeOperateRecordService.addGoods(goods)
        } catch (e: DataIntegrityViolationException) {
            throw ResponseStatusException(HttpStatus.BAD_REQUEST, "EXIST_BARCODE")
        }
    }

    @Transactional(readOnly = true)
    fun queryGoods(
        queryGoodsRequest: GoodsController.QueryGoodsRequest,
        pageable: Pageable
    ): Page<GoodsController.QueryGoodsResponse> {
        val employee = employeeService.getRequiredCurrentEmployee()

        return queryGoodsPage(queryGoodsRequest, pageable).map { goods ->
            GoodsController.QueryGoodsResponse(
                goods.id,
                goods.name,
                goods.barcode,
                goods.price,
                goods.amount,
                goods.createdDateTime,
                goods.lastModifiedDateTime,
                goods.brand?.toIntIdAndName(),
                goods.category?.toIntIdAndName(),
                goods.factory?.toIntIdAndName(),
                goods.boxSpecification,
                if (employee.authorities.contains(Authority.货品_进价_查看))
                    goods.costPrice
                else null,
                goods.units.map {
                    GoodsController.GoodsUnitResponse(it.id!!, it.name, it.basicMultiple, it.updatable)
                },
                goods.registrationCertificateNumber,
                goods.recordFilingNumber,
                goods.expressionPeriod,
                goods.executiveStandard,
                goods.subGoodsList.size,
                if (queryGoodsRequest.forEdit) {
                    goods.subGoodsList.map {
                        GoodsController.GoodsDetail.SubGoodsResponse(
                            it.id!!,
                            it.name,
                            it.number,
                            it.amount,
                        )
                    }
                } else emptyList(),
                goods.compositeGoodsList.size,
                goods.disable,
            )
        }
    }

    private fun queryGoodsPage(
        queryGoodsRequest: GoodsController.QueryGoodsRequest,
        pageable: Pageable
    ): Page<Goods> {
        val systemService = systemServiceService.getCurrentSystemService()
        return this.goodsRepository.findAll(
            GoodsRepository.Specifications.fromQueryGoods(queryGoodsRequest).and(
                SystemServiceRepository.Specifications.systemServiceEquals(
                    { root -> root },
                    systemServiceService.getCurrentSystemService()
                )
            ).and(
                if (queryGoodsRequest.onlyNew) {
                    val now = OffsetDateTime.now()
                    GoodsRepository.Specifications.createdDateTimeBetween(
                        now.minusDays(systemService.setting.isNewGoodsAfterCreatedDays.toLong()),
                        now
                    )
                } else {
                    null
                }
            ), pageable
        )
    }

    @Suppress("DuplicatedCode")
    @Transactional
    fun updateGoods(id: Int, addGoodsRequest: GoodsController.AddGoodsRequest) {
        val goods = this.goodsRepository.findByIdOrNull(id).orElse404()
        val currentSystemService = systemServiceService.getCurrentSystemService()
        with(goods) {
            name = addGoodsRequest.name
            barcode = addGoodsRequest.barcode
            price = addGoodsRequest.price
            amount = addGoodsRequest.amount
            brand = if (brand?.id == addGoodsRequest.brandId) brand else addGoodsRequest.brandId?.let {
                brandRepository.findByIdAndSystemService(it, currentSystemService)
            }
            category = if (category?.id == addGoodsRequest.categoryId) category else addGoodsRequest.categoryId?.let {
                categoryRepository.findByIdAndSystemService(it, currentSystemService)
            }
            boxSpecification = addGoodsRequest.boxSpecification
            costPrice = addGoodsRequest.costPrice
            registrationCertificateNumber = addGoodsRequest.registrationCertificateNumber
            recordFilingNumber = addGoodsRequest.recordFilingNumber
            expressionPeriod = addGoodsRequest.expressionPeriod
            note = addGoodsRequest.note.nullIfBlank()
            executiveStandard = addGoodsRequest.executiveStandard
            val deletedUnits = units.filter { unit ->
                addGoodsRequest.units == null || addGoodsRequest.units.none { unit.id == it.id }
            }
            goodsUnitRepository.deleteAll(deletedUnits)
            units = addGoodsRequest.units?.map {
                GoodsUnit().apply {
                    this.id = it.id
                    name = it.name
                    basicMultiple = it.basicMultiple
                    this.goods = goods
                }
            }?.toMutableList() ?: mutableListOf()
            subGoodsList = addGoodsRequest.subGoodsList.map {
                SubGoods().apply {
                    this.id = it.id
                    number = it.number
                    name = it.name
                    amount = it.amount
                    this.goods = goods
                }
            }.toMutableList()
        }
        this.goodsRepository.save(goods)
        this.employeeOperateRecordService.updateGoods(goods)
    }

    @Transactional(readOnly = true)
    fun getAllGoodsSummaries(queryGoodsRequest: GoodsController.QueryGoodsRequest): List<GoodsController.GoodsSummary> {
        return this.goodsRepository.findAll(
            GoodsRepository.Specifications.fromQueryGoods(queryGoodsRequest).and(
                SystemServiceRepository.Specifications.systemServiceEquals(
                    { r -> r },
                    systemServiceService.getCurrentSystemService()
                )
            )
        ).map { goods ->
            GoodsController.GoodsSummary(goods.id, goods.name, goods.barcode, goods.units.map {
                QueryGoodsUnitResponse(it.id!!, it.name, it.basicMultiple)
            }, goods.subGoodsList.map {
                GoodsController.GoodsDetail.SubGoodsResponse(it.id!!, it.name, it.number, it.amount)
            })
        }
    }

    fun exists(barcode: String): Boolean {
        return this.goodsRepository.existsBySystemServiceAndBarcodeAndDisableIsFalse(
            systemServiceService.getCurrentSystemService(),
            barcode
        )
    }

    fun queryGoodsSummaries(
        queryGoodsRequest: GoodsController.QueryGoodsRequest,
        pageable: Pageable
    ): Page<GoodsController.GoodsBasic> {
        return queryGoodsPage(queryGoodsRequest, pageable).map {
            GoodsController.GoodsBasic(
                it.id,
                it.name,
                it.barcode,
                it.price,
                it.amount
            )
        }
    }

    @Transactional
    fun importFromExcelItems(goodsItems: List<GoodsController.CreateGoodsItem>): Int {
        val currentSystemService = systemServiceService.getCurrentSystemService()
        val goodsList = goodsItems.distinctBy {
            it.barcode
        }.filterNot {
            this.goodsRepository.existsBySystemServiceAndBarcodeAndDisableIsFalse(currentSystemService, it.barcode)
        }.map { goodsItem ->
            Goods().apply {
                name = goodsItem.name
                barcode = goodsItem.barcode
                price = BigDecimal(goodsItem.price)
                amount = goodsItem.amount ?: 0
                category = goodsItem.category?.let {
                    categoryRepository.findFirstByNameAndSystemService(it, currentSystemService)
                        ?: categoryRepository.save(GoodsCategory().apply {
                            name = it
                            systemService = currentSystemService
                        })
                }
                brand = goodsItem.brand?.let {
                    brandRepository.findFirstByNameAndSystemService(it, currentSystemService) ?: brandRepository.save(
                        GoodsBrand().apply {
                            name = it
                            systemService = currentSystemService
                        })
                }
                boxSpecification = goodsItem.boxSpecification
                goodsItem.unit?.let {
                    units.add(GoodsUnit().also { goodsUnit ->
                        goodsUnit.name = goodsItem.unit
                        goodsUnit.basicMultiple = 0
                        goodsUnit.goods = this
                    })
                }
                registrationCertificateNumber = goodsItem.registrationCertificateNumber
                recordFilingNumber = goodsItem.recordFilingNumber
                expressionPeriod = goodsItem.expressionPeriod
                systemService = currentSystemService
            }
        }
        this.goodsRepository.saveAll(goodsList)
        val size = goodsList.size
        this.employeeOperateRecordService.importFromExcelItems(size)
        return size
    }

    @Transactional(readOnly = true)
    fun getGoodsDetail(barcode: String): GoodsController.GoodsDetail {
        val employee = employeeService.getRequiredCurrentEmployee()
        val goods =
            this.goodsRepository.findByBarcodeAndSystemServiceAndDisableIsFalse(barcode, employee.systemService)
                .orElse404()
        return GoodsController.GoodsDetail.fromGoods(goods, employee)
    }

    @Transactional(readOnly = true)
    fun getGoodsDetail(id: Int): GoodsController.GoodsDetail {
        val employee = employeeService.getRequiredCurrentEmployee()
        val goods =
            this.goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(id, employee.systemService)
                .orElse404()
        return GoodsController.GoodsDetail.fromGoods(goods, employee)
    }

    @Transactional(readOnly = true)
    fun getAllGoodsSummaryWithMarketInStore(marketId: Int): List<GoodsSummaryWithMarketInStore> {
        return this.goodsRepository.getAllGoodsSummaryWithMarketInStore(marketId)
            .map { GoodsSummaryWithMarketInStore.fromGoodsAndMarketInStore(it) }
    }

    @Transactional(readOnly = true)
    fun exportGoodsToExcel(
        fields: Array<GoodsExcelField>,
        queryGoodsRequest: GoodsController.QueryGoodsRequest,
        sort: Sort
    ): Resource {
        val list = this.goodsRepository.findAll(
            GoodsRepository.Specifications.fromQueryGoods(queryGoodsRequest).and(
                SystemServiceRepository.Specifications.systemServiceEquals(
                    { r -> r },
                    systemServiceService.getCurrentSystemService()
                )
            ),
            sort,
        )
        val workbook = exportGoodsDataToWorkbook(fields, list.map { goods ->
            GoodsRow(
                goods.name,
                goods.brand?.name,
                goods.price,
                goods.amount,
                goods.category?.name,
                goods.factory?.name,
                goods.barcode,
                goods.category?.name,
                goods.factory?.name
            )
        })
        return workbookToInputStreamResource(workbook)
    }


    @Transactional
    fun getAllGoodsSummaryWithMarketInStoreInMarket(marketId: Int): List<GoodsSummaryWithMarketInStore> {
        return this.goodsRepository.getAllGoodsSummaryWithMarketInStoreInMarket(marketId)
            .map { GoodsSummaryWithMarketInStore.fromGoodsAndMarketInStore(it) }
    }

    @Transactional
    fun deleteGoods(id: Int) {
        this.goodsRepository.deleteById(id)
    }

    @Transactional
    fun batchUpdateGoods(request: GoodsController.BatchUpdateGoodsRequest) {
        val goodsList = this.goodsRepository.findAllById(request.goodsIds)
        if (request.goodsIds.size != goodsList.size) {
            throw ResponseStatusException(HttpStatus.NOT_FOUND, "NO_GOODS_FOUND")
        }
        for (goods in goodsList) {
            request.categoryId?.let {
                goods.category = this.categoryRepository.findByIdOrNull(it)
            }
            request.brandId?.let {
                goods.brand = this.brandRepository.findByIdOrNull(it)
            }
            request.boxSpecification?.let {
                goods.boxSpecification = it
            }
            request.expressionPeriod?.let {
                goods.expressionPeriod = it
            }
            request.amount?.let {
                goods.amount = it
            }
            request.costPrice?.let {
                goods.costPrice = it
            }
            request.price?.let {
                goods.price = it
            }
        }
        this.goodsRepository.saveAll(goodsList)
    }

    @Transactional(readOnly = true)
    fun queryGoodsSummariesByPage(
        queryGoodsRequest: GoodsController.QueryGoodsRequest,
        pageable: Pageable
    ): Page<GoodsController.GoodsSummary> {
        return this.goodsRepository.findAll(
            GoodsRepository.Specifications.fromQueryGoods(queryGoodsRequest).and(
                SystemServiceRepository.Specifications.systemServiceEquals(
                    { r -> r },
                    systemServiceService.getCurrentSystemService()
                )
            ), pageable
        ).map { goods ->
            GoodsController.GoodsSummary(goods.id, goods.name, goods.barcode, goods.units.map {
                QueryGoodsUnitResponse(it.id!!, it.name, it.basicMultiple)
            }, goods.subGoodsList.map {
                GoodsController.GoodsDetail.SubGoodsResponse(it.id!!, it.name, it.number, it.amount)
            })
        }
    }

    @Transactional
    fun enableGoods(id: Int) {
        val goods = this.goodsRepository.findByIdAndSystemServiceAndDisableIsTrue(id,systemServiceService.getCurrentSystemService()).orElse404()
        goods.disable = false
        this.goodsRepository.save(goods)
        this.employeeOperateRecordService.enableGoods(goods)
    }

    @Transactional
    fun disableGoods(id: Int) {
        val goods = this.goodsRepository.findByIdAndSystemServiceAndDisableIsFalse(id,systemServiceService.getCurrentSystemService()).orElse404()
        goods.disable = true
        this.goodsRepository.save(goods)
        this.employeeOperateRecordService.disableGoods(goods)
    }

    @Transactional
    fun batchEnableGoods(goodsIds: List<Int>) {
        val goodsList =
            this.goodsRepository.findByIdInAndSystemServiceAndDisableIsTrue(
                goodsIds,
                systemServiceService.getCurrentSystemService()
            )
        for (goods in goodsList) {
            goods.disable = false
        }
        this.goodsRepository.saveAll(goodsList)
        this.employeeOperateRecordService.batchEnableOrDisableGoods(goodsList,true)
    }


    @Transactional
    fun batchDisableGoods(goodsIds: List<Int>) {
        val goodsList =
            this.goodsRepository.findByIdInAndSystemServiceAndDisableIsFalse(
                goodsIds,
                systemServiceService.getCurrentSystemService()
            )
        for (goods in goodsList) {
            goods.disable = true
        }
        this.goodsRepository.saveAll(goodsList)
        this.employeeOperateRecordService.batchEnableOrDisableGoods(goodsList,false)
    }

}
